腾讯最难lua面试题答案及解析 您所在的位置:网站首页 lua 面试题 腾讯最难lua面试题答案及解析

腾讯最难lua面试题答案及解析

2024-04-28 00:40| 来源: 网络整理| 查看: 265

最近看到了一道号称腾讯最难的lua面试题:

研究了一下答案实现的过程,发现这题主要考察了元表和元方法、面向对象、环境这三个知识点,非常值得学习。题目如下:

function class(...) --TODO end --TODO补充修改实现class方法 A = class { name = string, age = int, foo = function() print('from A', name, age) end } B = class { __super = A, foo = function() print('from B', name, age) end } local a = A() a.name = 'hanmeimei' a.age = 17 a:foo() local b = B() b.name = 'lilei' b.age = 18 b:foo() a.name = 20 a.age = '20' b.foo = 'x' -- 输出 -- from A hanmeimei 17 -- from B lilei 18 -- 类型不匹配:name 的类型是 string -- 类型不匹配:age 的类型是 number -- 函数不能赋值

(string和int必须修改,可替换为任意实值)

我觉得这题可以分为三步解决:

1、__newindex元方法实现错误赋值提示

输入:

输出:

解析:

1、若以类似函数的形式调用了一个值(table)时,则会调用它的__call元方法(此处a=A()而非a=A,因此会调用A的__call),因此用__call返回一个实例来完成a=A()的调用

2、对table中不存在的字段进行访问时,会调用__index;当对table中不存在的字段进行赋值时,会调用__newindex元。因此可构造一个不存在任何键值对的table,用data储存数据并赋值给__index,__newindex实现赋值类型检测并输出提示。

最初版本代码:

2、在全局环境中设置全局变量name和age实现foo()输出

输入:

输出:

解析:

1、foo()中对name和age的调用没有传入任何self参数,因此要在class中实现调用而不改变foo()结构,则可在访问foo()前将name和age放入全局环境中。由于访问foo()时会调用__index(),因此该步骤可在__index中实现。

2、设置全局环境变量时,由于lua 5.1之后的版本没有setfenv,因此需构造setfenv

代码实现:

setenv

__index

3、实现B()的继承

输入:

输出:

解析:

B的data继承自A,而非直接传入,因此改写local data = ...,将父类及...中的成员都复制到data中

代码实现:

 

经过以上主要步骤,稍作修改,即可实现最终要求。完整版代码:

function class(...) -- 返回的表类似“类”的定义 local cls = {} -- 数据都存储在这里面,用作数据存储空间 local data = {} -- 复制传入...的的成员。传入的...类似类定义中的成员列表。需要将父类,以及...内的成员都复制过来 local function copyField(src, dest) -- 先复制父类 if src['__super'] then local superMeta = getmetatable(src['__super']) if superMeta and superMeta['__data'] then for k, v in pairs(superMeta['__data']) do dest[k] = v end end end -- 再复制子类,如果有重名,子类会覆盖父类 for k, v in pairs(src) do if k ~= '__super' then dest[k] = v end end end copyField(..., data) -- lua 5.1之后的版本没有setfenv,引入这段代码解决setfenv问题 if not setfenv then -- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html -- this assumes f is a function local function findenv(f) local level = 1 repeat local name, value = debug.getupvalue(f, level) if name == '_ENV' then return level, value end level = level + 1 until name == nil return nil end getfenv = function(f) return (select(2, findenv(f)) or _G) end setfenv = function(f, t) local level = findenv(f) if level then debug.setupvalue(f, level, t) end return f end end -- 设置cls的元表 setmetatable( cls, { -- 数据存储空间 __data = data, -- __newindex处理赋值时的相关逻辑,类似set __newindex = function(t, key, newValue) local oldValue = data[key] -- 根据旧值的类型,判断新值的类型是否相同,不相同打印提示,相同才赋值 if oldValue then if type(oldValue) == 'function' then print('函数不能赋值') return else if type(newValue) ~= type(data[key]) then print('类型不匹配:', tostring(key), ' 的类型是', type(data[key])) return end end end -- 将新值赋值 data[key] = newValue end, -- __index处理获取值时的相关逻辑,类似get __index = function(t, key) if data[key] then local value = data[key] --[[" 下面代码主要处理的是:A和B的foo函数体中都没有使用self,此时就得从全局表中获取该变量的值 foo = function() print('from A', name, age) end "]] if type(value) == 'function' then -- 新建一个全局表 local newG = {} setmetatable(newG, {__index = _G}) -- 将数据赋值到全局表中 for k, v in pairs(data) do if type(v) ~= 'function' then newG[k] = v end end -- 设置函数的全局环境表 setfenv(value, newG) end -- 返回原始值 return value end return nil end, -- __call可以使class创建的对象被调用,类似构造函数的用法,调用后复制出来一个实例 __call = function() return cls end } ) return cls end A = class { name = '', age = 0, foo = function() print('from A', name, age) end } B = class { __super = A, foo = function() print('from B', name, age) end } local a = A() a.name = 'hanmeimei' a.age = 17 a:foo() local b = B() b.name = 'lilei' b.age = 18 b:foo() a.name = 20 a.age = '20' b.foo = 'x'

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有